package org.invigorare.auctions.comm;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;

import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;
import org.invigorare.Utils;
import org.invigorare.auctions.comm.EntrySyncResult.Status;
import org.invigorare.auctions.data.AppSettings;
import org.invigorare.auctions.data.entities.AuctionSale;

import android.app.Activity;
import android.content.Context;
import android.util.Log;

/**
 * Interface between this application's requests for data and 
 * the remote web server that contains the master database.
 * @author jchern
 *
 */
public class WebServiceLayer {

	public String webserviceUrl;
	
	public static final String ACTION = "action";
	public static final String LASTUPDATE = "lastupdate";
	public static final String USERID = "id";
	
	public static final String ACTION_UPDATE = "update";
	public static final String ACTION_SUBMIT_SALE = "submit";
	
	public static final String ERROR_NODATA = "ERROR_NO_WEB_DATA";
	public static final String ERROR_UNRESOLVED_HOST = "ERROR_UNRESOLVED_HOST";
	
	HttpClient client = new DefaultHttpClient();
	HttpPost post = new HttpPost();
	HttpGet get = new HttpGet();
	
	public WebServiceLayer(Context context) {
		webserviceUrl = new AppSettings(context).getWebserviceURL();
	}
	
	private static String inboundRequest_Get(long lastUpdateTime, String userId) {
		return encodePostParams(inboundRequestPairs(lastUpdateTime, userId));
	}
	
	private static List<NameValuePair> inboundRequestPairs(long lastUpdateTime, String userId) {
		List<NameValuePair> pairs = new ArrayList<NameValuePair>();
		
		pairs.add(new BasicNameValuePair(ACTION, ACTION_UPDATE));
		pairs.add(new BasicNameValuePair(LASTUPDATE, String.valueOf(lastUpdateTime)));
		pairs.add(new BasicNameValuePair(USERID, userId));

		return pairs;
	}
	
	private static String outboundRequest_GetPortionOnly() {
		return encodePostParams(outboundUpdate_GetPairsOnly());
	}
	
	/**
	 * Returns a list of Name-Value pairs 
	 * for the GET portion of an outbound (submit action) request.
	 * @param saleEntry
	 * @return
	 */
	private static List<NameValuePair> outboundUpdate_GetPairsOnly() {
		List<NameValuePair> pairs = new ArrayList<NameValuePair>();
		pairs.add(new BasicNameValuePair(ACTION, ACTION_SUBMIT_SALE));
		return pairs;
	}
	
	/**
	 * Returns a list of Name-Value pairs 
	 * for the POST portion of an outbound (submit action) request.
	 * @param saleEntry
	 * @return
	 */
	private static List<NameValuePair> outboundRequest_PostPairs(AuctionSale saleEntry) {
		List<NameValuePair> pairs = new ArrayList<NameValuePair>();
		
		for (String webKey : AuctionSale.WEB_KEYS) {
			pairs.add(new BasicNameValuePair(webKey, saleEntry.get_webKey(webKey)));
		}
		
		return pairs;
	}

	/**
	 * Converts a list of Name-Value pairs into
	 * an appropriately formatted GET request parameter string.
	 * @param pairs
	 * @return
	 */
	private static String encodePostParams(List<NameValuePair> pairs) {
		StringBuilder sb = new StringBuilder();
		
		for (NameValuePair pair : pairs) {
			sb.append(pair.getName());
			sb.append('=');
			sb.append(pair.getValue());
			sb.append('&');
		}
		
		sb.deleteCharAt(sb.length()-1); // delete the extraneous last '&' character
		
		return sb.toString();
	}
	

	/**
	 * Makes an Inbound Update request to the webservice and returns
	 * the resulting data as a String (or null if something went wrong).
	 * @param currentActivity
	 * @param lastUpdateTime
	 * @param userId
	 * @return
	 */
	public String fetchInboundUpdate(Activity currentActivity, long lastUpdateTime, String userId) throws NoConnectionException {
		/* Check Connection -- fail and quit if not there */
		checkConnectivity(currentActivity);
		
		/* Form URL */
		URI uri;
		try {
			uri = new URI(webserviceUrl + '?' + inboundRequest_Get(lastUpdateTime, userId));
			get.setURI(uri);
		} catch (URISyntaxException e) {
			e.printStackTrace();
			return ERROR_NODATA;
		}
		
		/* Request Data */
		String data = ERROR_NODATA;
		try {
			HttpResponse response = client.execute(get);
			Log.i("WEBLAYER", "executed get: " + uri.toString());
			data = EntityUtils.toString(response.getEntity());
		} catch (ClientProtocolException e) {
			Utils.showToast(currentActivity, "ClientProtocolException");
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
			Utils.showToast(currentActivity, "IOException");
			data = ERROR_UNRESOLVED_HOST;
		}

		return data;
	}
	
	/**
	 * Generates and POSTs a submit request for each AuctionSale entry in the provided list.
	 * @param currentActivity
	 * @param sales
	 * @return a DbSyncStatus object containing a list of EntrySyncResults, corresponding to the success or failure code of each entry's submission.
	 */
	public DbSyncResult doOutboundSaleUpdates(Activity currentActivity, List<AuctionSale> sales) {
		
		ArrayList<EntrySyncResult> results = new ArrayList<EntrySyncResult>(sales.size());
		
		/* Upload one entry per request */
		for (AuctionSale sale : sales) {
			EntrySyncResult result;
			try {
				result = doOneOutboundUpdate(currentActivity, sale);
			} catch (NoConnectionException e) {
				e.printStackTrace();
				result = new EntrySyncResult(Status.NO_CONNECTION, e);
			}
			results.add(result);
		}
		
		return new DbSyncResult("Sale Entries", null, results); 
	}

	/**
	 * Try to send one outbound AuctionSale sale entry
	 * @param currentActivity
	 * @param saleEntry
	 * @return the success or failure code for this entry
	 * @throws NoConnectionException
	 */
	private EntrySyncResult doOneOutboundUpdate(Activity currentActivity, AuctionSale saleEntry) throws NoConnectionException {
		/* Check Connection -- fail and quit if not there */
		checkConnectivity(currentActivity);
		
		/* Form URL */
		URI uri;
		try {
			uri = new URI(webserviceUrl + '?' + outboundRequest_GetPortionOnly());
			post.setURI(uri);
		} catch (URISyntaxException e) {
			e.printStackTrace();
			return new EntrySyncResult(Status.EXCEPTION, e);
		}
		
		/* Form and Transmit Request */ 
		try {
			Log.i("WEBLAYER", "generating post for auction: \n" + saleEntry.toString());
			List<NameValuePair> postValues = outboundRequest_PostPairs(saleEntry);
			UrlEncodedFormEntity entity = new UrlEncodedFormEntity(postValues, HTTP.UTF_8);
			post.setEntity(entity);

			HttpResponse response = client.execute(post);

			Log.i("WEBLAYER", "executed post to url: " + uri.toString());
			Log.i("WEBLAYER", "executed post for auction sold at: " + saleEntry.get_webKey(AuctionSale.WEB_SALE_TIME));

			/* Check Result */
			// Check response entity (data) to determine success or failure */
			// A successful response returns an integer, telling the rowId of the newly submitted entry on the server */
			String responseData = EntityUtils.toString(response.getEntity());
			Log.i("WEBLAYER.SUBMIT", "got response:\n" + responseData);

			if (outboundUpdateResponseWasValid(responseData)) {
				return new EntrySyncResult(Status.SUCCESS, null);
			} else {
				return new EntrySyncResult(Status.SERVER_ERROR, responseData);
			}
			
			
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
			return new EntrySyncResult(Status.EXCEPTION, e);
		} catch (ClientProtocolException e) {
			e.printStackTrace();
			return new EntrySyncResult(Status.EXCEPTION, e);
		} catch (IOException e) {
			e.printStackTrace();
			return new EntrySyncResult(Status.EXCEPTION, e);
		}
	}

	/**
	 * Checks whether the response data is valid by attempting to convert it to an integer (serverside rowId).
	 * If the response string could not be parsed into an integer, then we don't consider it valid.
	 * @param responseData
	 * @return
	 */
	private boolean outboundUpdateResponseWasValid(String responseData) {
		try {
			int rowId = Integer.parseInt(responseData);
			return true;
		} catch (NumberFormatException e) {
			return false;
		}
	}
	
	private void checkConnectivity(Activity currActivity) throws NoConnectionException {
		checkConnectivity(currActivity.getApplicationContext());
	}
	
	private void checkConnectivity(Context context) throws NoConnectionException {
		if (!Connectivity.hasConnection(context))
			throw new NoConnectionException();
	}
	
}
